/*
 * The builtin Math object.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /usr/local/cvsroot/ngs/js/src/b_math.c,v $
 * $Id: b_math.c,v 1.2 1998/09/08 12:29:01 mtr Exp $
 */

#include "jsint.h"

/*
 * Types and definitions.
 */

#ifndef M_E
#define M_E		2.71828182845904523536028747135266250
#endif

#ifndef M_LN10
#define M_LN10		2.302585092994046
#endif

#ifndef M_LN2
#define M_LN2		0.693147180559945309417232121458176568
#endif

#ifndef M_LOG10E
#define M_LOG10E	0.434294481903251827651128918916605082
#endif

#ifndef M_LOG2E
#define M_LOG2E		1.44269504088896340735992468100189214
#endif

#ifndef M_PI
#define M_PI		3.14159265358979323846264338327950288
#endif

#ifndef M_SQRT1_2
#define M_SQRT1_2	0.707106781186547524400844362104849039
#endif

#ifndef M_SQRT2
#define M_SQRT2		1.41421356237309504880168872420969808
#endif

#define ONE_ARG() \
do { \
    JSNode cvt; \
 \
    if (args->u.vinteger != 1) \
        goto argument_error; \
 \
    js_vm_to_number (vm, &args[1], &cvt); \
    if (cvt.type == JS_INTEGER) \
        d = (JSFloat) cvt.u.vinteger; \
    else if (cvt.type == JS_FLOAT) \
        d = cvt.u.vfloat; \
    else \
    { \
	    /* Must be NaN. */ \
	    result_return->type = JS_NAN; \
	    goto done; \
    } \
} while (0)

#define TWO_ARGS() \
do { \
    JSNode cvt; \
 \
    if (args->u.vinteger != 2) \
        goto argument_error; \
 \
    js_vm_to_number (vm, &args[1], &cvt); \
    if (cvt.type == JS_INTEGER) \
        d = (JSFloat) cvt.u.vinteger; \
    else if (cvt.type == JS_FLOAT) \
        d = cvt.u.vfloat; \
    else \
    { \
	    /* Must be NaN. */ \
	    result_return->type = JS_NAN; \
	    goto done; \
    } \
 \
    js_vm_to_number (vm, &args[2], &cvt); \
    if (cvt.type == JS_INTEGER) \
        d2 = (JSFloat) args[1].u.vinteger; \
    else if (cvt.type == JS_FLOAT) \
        d2 = cvt.u.vfloat; \
    else \
    { \
	    /* Must be NaN. */ \
	    result_return->type = JS_NAN; \
	    goto done; \
    } \
} while (0)

/* Class context. */
struct math_ctx_st {
	JSSymbol s_abs;
	JSSymbol s_acos;
	JSSymbol s_asin;
	JSSymbol s_atan;
	JSSymbol s_atan2;
	JSSymbol s_ceil;
	JSSymbol s_cos;
	JSSymbol s_exp;
	JSSymbol s_floor;
	JSSymbol s_log;
	JSSymbol s_max;
	JSSymbol s_min;
	JSSymbol s_pow;
	JSSymbol s_random;
	JSSymbol s_round;
	//JSSymbol s_seed;
	JSSymbol s_sin;
	JSSymbol s_sqrt;
	JSSymbol s_tan;

	JSSymbol s_E;
	JSSymbol s_LN10;
	JSSymbol s_LN2;
	JSSymbol s_LOG10E;
	JSSymbol s_LOG2E;
	JSSymbol s_PI;
	JSSymbol s_SQRT1_2;
	JSSymbol s_SQRT2;
};

typedef struct math_ctx_st MathCtx;

/*
 * Static functions.
 */

/* Method proc. */
static int
method(JSVirtualMachine * vm, JSBuiltinInfo * builtin_info,
	   void *instance_context, JSSymbol method, JSNode * result_return, JSNode * args)
{
	MathCtx *ctx = builtin_info->obj_context;
	JSFloat d, d2;
	int i;

	/* The default return value. */
	result_return->type = JS_FLOAT;

	if (method == ctx->s_abs) {
		ONE_ARG();
		result_return->u.vfloat = fabs(d);
	}
	// --------------------
	else if (method == ctx->s_acos) {
		ONE_ARG();
		result_return->u.vfloat = acos(d);
	}
	// --------------------
	else if (method == ctx->s_asin) {
		ONE_ARG();
		result_return->u.vfloat = asin(d);
	}
	// --------------------
	else if (method == ctx->s_atan) {
		ONE_ARG();
		result_return->u.vfloat = atan(d);
	}
	// --------------------
	else if (method == ctx->s_atan2) {
		TWO_ARGS();
		result_return->u.vfloat = atan2(d, d2);
	}
	// --------------------
	else if (method == ctx->s_ceil) {
		ONE_ARG();
		result_return->u.vfloat = ceil(d);
	}
	// --------------------
	else if (method == ctx->s_cos) {
		ONE_ARG();
		result_return->u.vfloat = cos(d);
	}
	// --------------------
	else if (method == ctx->s_exp) {
		ONE_ARG();
		result_return->u.vfloat = exp(d);
	}
	// --------------------
	else if (method == ctx->s_floor) {
		ONE_ARG();
		result_return->u.vfloat = floor(d);
	}
	// --------------------
	else if (method == ctx->s_log) {
		ONE_ARG();
		result_return->u.vfloat = log(d);
	}
	// --------------------
	else if (method == ctx->s_max || method == ctx->s_min) {
		JSNode cvt;

		if (args->u.vinteger < 1)
			goto argument_error;

		/* Take the initial argument. */
		js_vm_to_number(vm, &args[1], &cvt);
		if (cvt.type == JS_NAN) {
			result_return->type = JS_NAN;
			goto done;
		}
		if (cvt.type == JS_INTEGER)
			d = (JSFloat) cvt.u.vinteger;
		else
			d = cvt.u.vfloat;

		/* Handle the rest. */
		for (i = 1; i < args->u.vinteger; i++) {
			js_vm_to_number(vm, &args[1], &cvt);
			if (cvt.type == JS_NAN) {
				result_return->type = JS_NAN;
				goto done;
			}
			if (cvt.type == JS_INTEGER)
				d2 = (JSFloat) cvt.u.vinteger;
			else
				d2 = cvt.u.vfloat;

			if (method == ctx->s_max) {
				if (d2 > d)
					d = d2;
			} else {
				if (d2 < d)
					d = d2;
			}
		}

		result_return->type = JS_FLOAT;
		result_return->u.vfloat = d;
	}
	// --------------------
	else if (method == ctx->s_pow) {
		TWO_ARGS();
		result_return->u.vfloat = pow(d, d2);
	}
	// --------------------
	else if (method == ctx->s_random) {
		if (args->u.vinteger != 0)
			goto argument_error;
#ifdef RANDOM_MAX
		result_return->u.vfloat = (JSFloat)random()/RANDOM_MAX;
#else
		result_return->u.vfloat = (JSFloat)rand()/RAND_MAX;
#endif
	}
	// --------------------
	else if (method == ctx->s_round) {
		ONE_ARG();
		result_return->type = JS_INTEGER;
		result_return->u.vinteger = (long) (d + 0.5);
	}
	// --------------------
	/*
	else if (method == ctx->s_seed) {
		ONE_ARG();
		//js_srand48(ctx->drand48_context, (long) d);
		result_return->type = JS_UNDEFINED;
	}
	*/
	// --------------------
	else if (method == ctx->s_sin) {
		ONE_ARG();
		result_return->u.vfloat = sin(d);
	}
	// --------------------
	else if (method == ctx->s_sqrt) {
		ONE_ARG();
		result_return->u.vfloat = sqrt(d);
	}
	// --------------------
	else if (method == ctx->s_tan) {
		ONE_ARG();
		result_return->u.vfloat = tan(d);
	}
	// --------------------
	else if (method == vm->syms.s_toString) {
		js_vm_make_static_string(vm, result_return, "Math", 4);
	}
	// --------------------
	else
		return JS_PROPERTY_UNKNOWN;

  done:

	return JS_PROPERTY_FOUND;


	/*
	 * Error handling.
	 */

  argument_error:
#ifdef JS_RUNTIME_WARNING
	sprintf(vm->error, "Math.%s(): illegal amount of arguments", js_vm_symname(vm, method));
#endif
	js_vm_error(vm);

	/* NOTREACHED. */
	return 0;
}

/* Property proc. */
static int
property(JSVirtualMachine * vm, JSBuiltinInfo * builtin_info,
		 void *instance_context, JSSymbol property, int set, JSNode * node)
{
	MathCtx *ctx = builtin_info->obj_context;

	/* The default result is float. */
	node->type = JS_FLOAT;

	if (property == ctx->s_E) {
		if (set)
			goto immutable;

		node->u.vfloat = M_E;
	} else if (property == ctx->s_LN10) {
		if (set)
			goto immutable;

		node->u.vfloat = M_LN10;
	} else if (property == ctx->s_LN2) {
		if (set)
			goto immutable;

		node->u.vfloat = M_LN2;
	} else if (property == ctx->s_LOG10E) {
		if (set)
			goto immutable;

		node->u.vfloat = M_LOG10E;
	} else if (property == ctx->s_LOG2E) {
		if (set)
			goto immutable;

		node->u.vfloat = M_LOG2E;
	} else if (property == ctx->s_PI) {
		if (set)
			goto immutable;

		node->u.vfloat = M_PI;
	} else if (property == ctx->s_SQRT1_2) {
		if (set)
			goto immutable;

		node->u.vfloat = M_SQRT1_2;
	} else if (property == ctx->s_SQRT2) {
		if (set)
			goto immutable;

		node->u.vfloat = M_SQRT2;
	} else {
		if (!set)
			node->type = JS_UNDEFINED;

		return JS_PROPERTY_UNKNOWN;
	}

	return JS_PROPERTY_FOUND;


	/*
	 * Error handling.
	 */

  immutable:
#ifdef JS_RUNTIME_WARNING
	sprintf(vm->error, "Math.%s: immutable property", js_vm_symname(vm, property));
#endif
	js_vm_error(vm);

	/* NOTREACHED. */
	return 0;
}


/*
 * Global functions.
 */

void js_builtin_Math(JSVirtualMachine * vm)
{
	JSBuiltinInfo *info;
	MathCtx *ctx;
	JSNode *n;

	ctx = js_calloc(vm, 1, sizeof(*ctx));

	ctx->s_abs = js_vm_intern(vm, "abs");
	ctx->s_acos = js_vm_intern(vm, "acos");
	ctx->s_asin = js_vm_intern(vm, "asin");
	ctx->s_atan = js_vm_intern(vm, "atan");
	ctx->s_atan2 = js_vm_intern(vm, "atan2");
	ctx->s_ceil = js_vm_intern(vm, "ceil");
	ctx->s_cos = js_vm_intern(vm, "cos");
	ctx->s_exp = js_vm_intern(vm, "exp");
	ctx->s_floor = js_vm_intern(vm, "floor");
	ctx->s_log = js_vm_intern(vm, "log");
	ctx->s_max = js_vm_intern(vm, "max");
	ctx->s_min = js_vm_intern(vm, "min");
	ctx->s_pow = js_vm_intern(vm, "pow");
	ctx->s_random = js_vm_intern(vm, "random");
	ctx->s_round = js_vm_intern(vm, "round");
	//ctx->s_seed = js_vm_intern(vm, "seed");
	ctx->s_sin = js_vm_intern(vm, "sin");
	ctx->s_sqrt = js_vm_intern(vm, "sqrt");
	ctx->s_tan = js_vm_intern(vm, "tan");

	ctx->s_E = js_vm_intern(vm, "E");
	ctx->s_LN10 = js_vm_intern(vm, "LN10");
	ctx->s_LN2 = js_vm_intern(vm, "LN2");
	ctx->s_LOG10E = js_vm_intern(vm, "LOG10E");
	ctx->s_LOG2E = js_vm_intern(vm, "LOG2E");
	ctx->s_PI = js_vm_intern(vm, "PI");
	ctx->s_SQRT1_2 = js_vm_intern(vm, "SQRT1_2");
	ctx->s_SQRT2 = js_vm_intern(vm, "SQRT2");

	/* Object information. */
	info = js_vm_builtin_info_create(vm);
	info->method_proc = method;
	info->property_proc = property;
	info->obj_context = ctx;
	info->obj_context_delete = js_free;

	/* Define it. */
	n = &vm->globals[js_vm_intern(vm, "Math")];
	js_vm_builtin_create(vm, n, info, NULL);
}
